#!/usr/bin/env node

/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
*/

var shell   = require('shelljs'),
    exec    = require('./exec'),
    spawn   = require('./spawn'),
    Q       = require('q'),
    path    = require('path'),
    fs      = require('fs'),
    log     = require('./log'),
    property  = require('./property');
    ROOT    = path.join(__dirname, '..', '..');
var binBinDir = "";
var check_reqs = require('./check_reqs');
var buildType="";        //bundle或者sandbox
var input_path,     //待签名apk目录
    output_path,    //签名后的apk输出目录
    keystore,       //证书路径
    alias,          //证书别名
    keypass,      //证书密码
    aliaspass;    //别名密码
var logPath = null;        //log文件路径
var LOCAL_PROPERTIES_TEMPLATE =
    '# This file is automatically generated.\n' +
    '# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';

function findApks(directory) {
    var ret = [];
    if (fs.existsSync(directory)) {
        fs.readdirSync(directory).forEach(function(p) {
            if (path.extname(p) == '.apk') {
                ret.push(path.join(directory, p));
            }
        });
    }
    return ret;
}

function sortFilesByDate(files) {
    return files.map(function(p) {
        return { p: p, t: fs.statSync(p).mtime };
    }).sort(function(a, b) {
        var timeDiff = b.t - a.t;
        return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
    }).map(function(p) { return p.p; });
}

function findOutputApksHelper(dir, build_type) {
    var ret = findApks(dir).filter(function(candidate) {
        // Need to choose between release and debug .apk.
        if (build_type === 'debug') {
            return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
        }
        if (build_type === 'release') {
            return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
        }
        return true;
    });
    ret = sortFilesByDate(ret);
    if (ret.length === 0) {
        return ret;
    }
    var archSpecific = !!/-x86|-arm/.exec(ret[0]);
    return ret.filter(function(p) {
        return !!/-x86|-arm/.exec(p) == archSpecific;
    });
}

function hasCustomRules() {
    return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
}

function extractProjectNameFromManifest(projectPath) {
    var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
    var manifestData = fs.readFileSync(manifestPath, 'utf8');
    var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
    if (!m) {
        throw new Error('Could not find activity name in ' + manifestPath);
    }
    return m[1];
}

function extractSubProjectPaths() {
    var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
    var ret = {};
    var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg
    var m;
    while (m = r.exec(data)) {
        ret[m[1]] = 1;
    }
    return Object.keys(ret);
}

function signature_apks() {
  batch_signature(input_path,output_path);
}

var builders = {
    ant: {
        getArgs: function(cmd) {
            var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
            // custom_rules.xml is required for incremental builds.
            if (hasCustomRules()) {
                args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
            }
            return args;
        },

        prepEnv: function() {
            return check_reqs.check_ant()
            .then(function() {
                // Copy in build.xml on each build so that:
                // A) we don't require the Android SDK at project creation time, and
                // B) we always use the SDK's latest version of it.
                var sdkDir = process.env['ANDROID_HOME'];
                var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
                function writeBuildXml(projectPath) {
                    var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
                    fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
                    if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
                        fs.writeFileSync(path.join(projectPath, 'local.properties'), LOCAL_PROPERTIES_TEMPLATE);
                    }
                }
                var subProjects = extractSubProjectPaths();
                writeBuildXml(ROOT);
                for (var i = 0; i < subProjects.length; ++i) {
                    writeBuildXml(path.join(ROOT, subProjects[i]));
                }
            });
        },

        /*
         * Builds the project with ant.
         * Returns a promise.
         */
        build: function(build_type) {
            // Without our custom_rules.xml, we need to clean before building.
            //build_config.setBuildMethod("ant");
            var ret = Q();
            if (!hasCustomRules()) {
                // clean will call check_ant() for us.
                ret = this.clean();
            }

            var builder = this;
            var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
            return check_reqs.check_ant()
            .then(function() {
                return spawn('ant', args);
            })
            //.then(function() {});
        },

        clean: function() {
            var args = this.getArgs('clean');
            return check_reqs.check_ant()
            .then(function() {
                return spawn('ant', args);
            });
        },

        findOutputApks: function(build_type) {
            var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
            return findOutputApksHelper(binDir, build_type);
        }
    },
    gradle: {
        getArgs: function(cmd) {
            var lintSteps;
            if (process.env['BUILD_MULTIPLE_APKS']) {
                lintSteps = [
                    'lint',
                    'lintVitalX86Release',
                    'lintVitalArmv7Release',
                    'compileLint',
                    'copyReleaseLint',
                    'copyDebugLint'
                ];
            } else {
                lintSteps = [
                    'lint',
                    //'lintVitalRelease', //编译变色龙v3此选项有问题
                    'compileLint',
                    //'copyReleaseLint',
                    //'copyDebugLint'
                ];
            }
              var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
              // 10 seconds -> 6 seconds
              args.push('-Dorg.gradle.daemon=true');
              // Excluding lint: 6s-> 1.6s
              for (var i = 0; i < lintSteps.length; ++i) {
                args.push('-x', lintSteps[i]);
              }
              // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
              // args.push('-Dorg.gradle.parallel=true');
              return args;
            //});

        },

        prepEnv: function() {
            return check_reqs.check_gradle()
            .then(function() {
                // Copy the gradle wrapper on each build so that:
                // A) we don't require the Android SDK at project creation time, and
                // B) we always use the SDK's latest version of it.
                  var projectPath = ROOT;
                  // check_reqs ensures that this is set.

                  var sdkDir = process.env['ANDROID_HOME'];

                  var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
                  if (process.platform == 'win32') {
                    shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath);
                  } else {
                    shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath);
                  }
                  shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
                  shell.mkdir('-p', path.join(projectPath, 'gradle'));
                  shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
                });

            //});
        },

        /*
         * Builds the project with gradle.
         * Returns a promise.
         */
        build: function(build_type) {
            var builder = this;
            //针对变色龙v3
            /*var mainPath = path.join(ROOT,'Master');
            var wrapper = path.join(mainPath, 'gradlew');
            var args = ['-b', path.join(mainPath, 'build.gradle'),buildType];*/
            var wrapper = path.join(ROOT,'gradlew');
            console.log(wrapper);
            var args = ['-b',path.join(ROOT,'build.gradle'),buildType];
            return Q().then(function() {
                return spawn(wrapper, args);
            }).then(function() {
              signature_apks();
            });
        },

        clean: function() {
            var builder = this;
            var wrapper = path.join(ROOT, 'gradlew');
            var args = builder.getArgs('clean');
            return Q().then(function() {
                return spawn(wrapper, args);
            });
        },

        findOutputApks: function(build_type) {
            var binDir =path.join(ROOT,'build','outputs','apk');
            return findOutputApksHelper(binDir, build_type);
        }
    },

    none: {
        prepEnv: function() {
            return Q();
        },
        build: function() {
            console.log('Skipping build...');
            return Q(null);
        },
        clean: function() {
            return Q();
        },
        findOutputApks: function(build_type) {
            return sortFilesByDate(builders.ant.findOutputApks(build_type).concat(builders.gradle.findOutputApks(build_type)));
        }
    }
};

function parseOpts(options) {
    // Backwards-compatibility: Allow a single string argument
    if (typeof options == "string") options = [options];
    var ret = {
        buildType: 'release',
        buildMethod: process.env['ANDROID_BUILD'] || 'gradle'
    };
    // Iterate through command line options
    for (var i=0; options && (i < options.length); ++i) {
        if (/^--/.exec(options[i])) {
            var option = options[i].substring(2);
            var selector = extractParamName(option);
            switch(selector) {
                case 'path':
                  ROOT = extractParamValue(option,'path');;
                  //input_path = path.join(ROOT,'MainActivity','build','outputs','apk');
                  input_path = path.join(ROOT,'build','outputs','apk');
                  break;
                case 'buildtype':
                  buildType = "build";
                  break;
                case 'apkdir':
                  output_path = extractParamValue(option,'apkdir');
                  break;
                case 'keystore':
                      keystore = extractParamValue(option,"keystore");
                      break;
                case 'alias':
                      alias = extractParamValue(option,"alias");
                      break;
                case 'keypass':
                      keypass = extractParamValue(option,"keypass");
                      break;
                case 'aliaspass':
                      aliaspass = extractParamValue(option,"aliaspass");
                      break;
                case 'configline':    //设置配置,支持多属性设置
                      //var configFile = path.join(ROOT,'BaseModule','src','main','res','raw','cube.properties');
                      var configFile = path.join(ROOT,'res','raw','cube.properties');
                      var configline = extractParamValue(option,"configline");

                      var idx = -1,key = null,value = null;
                      var configs = configline.split(',');
                      property.init(configFile);
                      for (ix in configs) {
                        idx = configs[ix].indexOf('=');
                        key = configs[ix].substring(0,idx);
                        value = configs[ix].substring(idx+1,configline.length);
                        property.setProperty(key,value);
                      };

                      property.save();
                      
                      break;
                case 'logpath':
                      logPath = extractParamValue(option,"logpath");
                      log.init(logPath);
                      break;
                default :
                    return Q.reject('Build option \'' + options[i] + '\' not recognized.');
            }
        } else {
            return Q.reject('Build option \'' + options[i] + '\' not recognized.');
        }
    }

    return ret;
}

/**
* @description 为apk签名
* @param unsignedApk	待签名的apk
* @param signedApk 	  签名后输出的apk
* @param keystore  	  签名证书路径
* @param alias			  证书别名
* @param keypasswd  	证书密码
* @param aliaspasswd 	证书别名密码
*/
var apk_signature = module.exports.apk_signature = function(unsignedApk, signedApk, keystore, alias, keypass, storepass, callback, notify) {

  var exec = require('child_process').exec;
  // jdk ver == 1.7时需加上-digestalg SHA1 -sigalg MD5withRSA，否则签名后的apk无法安装
  var cmd = "jarsigner -verbose -keystore " + keystore  + " -keypass " + keypass + " -storepass " +
  storepass + " -signedjar " + signedApk + " " + unsignedApk + " " + alias + " -digestalg SHA1 -sigalg MD5withRSA ";
  console.log('签名cmd ---> ' + cmd);

  var opt = exec(cmd, {maxBuffer: 10*1024*1024}, function(err, stdout, stderr) {
    if(err) {
      console.error(err);
    }
  });

  opt.on('exit', function (code, signal) {
    if(callback !== '' && typeof callback === "function") {
      callback(code, signal);
    }
  });

  opt.stdout.on('data',function(data) {
    if(notify) {
      notify({stdout: data.toString()});
    }
  });

  opt.stderr.on('data',function(data) {
    if(notify) {
      notify({stderr: data.toString()});
    }
    log.save(-1,"signature apk failed!");
  });
}

var signature_apk = module.exports.signature_apk = function(unsignedApk, signedApk, keystore, alias, keypass, storepass) {
  var args = ['-verbose','-keystore',keystore,'-keypass',keypass,'-storepass',storepass,'-signedjar',signedApk,unsignedApk,alias,'-digestalg','SHA1','-sigalg','MD5withRSA'];
  spawn('jarsigner',args);
}

/**
* 批量签名
*@param unsignedApkDir 待签名的apk目录
*@param singedApkDir 签名后输出的apk目录
*/
var batch_signature = function(unsignedApkDir,signedApkDir,callback) {

  var fs = require('fs');
  if(fs.existsSync(signedApkDir)) {
    shell.rm('-rf', signedApkDir);
  }
  try{
    fs.mkdirSync(signedApkDir);
  }catch(err){
    console.log(err);
  }

  var fileList = [];
  var walk = function(dir) {
    var files = fs.readdirSync(dir);
    files.forEach(function(item) {
      var tmpPath = require('path').join(dir,item);
      var stats = fs.statSync(tmpPath);
      if(stats.isDirectory()) {
        walk(tmpPath);
      } else {
        if(item.indexOf("apk") != -1 && item.indexOf("release") != -1 /*&& && item.indexOf(buildType) != -1*/) {
          console.log('------------------item---------------->:' + item);
          var signedApk = require('path').join(signedApkDir,"app.apk");
          apk_signature(tmpPath,signedApk,keystore,alias,keypass,aliaspass,callback);
        }
      }
    });
  };
  walk(unsignedApkDir);

}


/**
* 获取指定参数的值
* @param option 输入参数
* @param param  提取的参数名
*/
var extractParamValue = function(option,param) {
  var value = "";
  if(option.indexOf(param) !=-1) {
    var start = option.indexOf('=') + 1;
    value = option.substring(start);
  }
  return value;
}

/**
*获取参数名
* @param option 输入参数
*/

var extractParamName = function(option) {
  var start = option.indexOf('=');
  var selector = option.substring(0,start);
  return selector;
}

/*
 * Builds the project with the specifed options
 * Returns a promise.
 */
module.exports.runClean = function(options) {
    var opts = parseOpts(options);
    var builder = builders['gradle'];
    return builder.prepEnv()
    .then(function() {
        return builder.clean();
    }).then(function() {
        shell.rm('-rf', path.join(ROOT, 'out'));
    });
};

/*
 * Builds the project with the specifed options
 * Returns a promise.
 */
module.exports.run = function(options) {
    var opts = parseOpts(options);
    var builder = builders['gradle'];
    return builder.prepEnv()
    .then(function() {
        return builder.build(opts.buildType);
    }).then(function() {
       var apkPaths = builder.findOutputApks(opts.buildType);
        console.log('Built the following apk(s):');
        console.log('    ' + apkPaths.join('\n    '));
        if(apkPaths == null || apkPaths == "") {
            log.save(-1,"build failed!");
        }
        return {
            apkPaths: apkPaths,
            buildType: opts.buildType,
            buildMethod: opts.buildMethod
        };
    });
};

/*
 * Detects the architecture of a device/emulator
 * Returns "arm" or "x86".
 */
module.exports.detectArchitecture = function(target) {
    return exec('adb -s ' + target + ' shell cat /proc/cpuinfo')
    .then(function(output) {
        if (/intel/i.exec(output)) {
            return 'x86';
        }
        return 'arm';
    });
};

module.exports.findBestApkForArchitecture = function(buildResults, arch) {
    var paths = buildResults.apkPaths.filter(function(p) {
        if (buildResults.buildType == 'debug') {
            return /-debug/.exec(p);
        }
        return !/-debug/.exec(p);
    });
    var archPattern = new RegExp('-' + arch);
    var hasArchPattern = /-x86|-arm/;
    for (var i = 0; i < paths.length; ++i) {
        if (hasArchPattern.exec(paths[i])) {
            if (archPattern.exec(paths[i])) {
                return paths[i];
            }
        } else {
            return paths[i];
        }
    }
    throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
};

module.exports.setProjectPath = function(path) {
    ROOT = path;
}

module.exports.getProjectPath = function() {
  return ROOT;
}

//设置apk的生成目录,这种主要针对类似变色龙v3这种目录结构和cordova默认的android工程目录树有点差异的工程
module.exports.setBinDirPath = function(path) {
  binBinDir = path;
}

module.exports.help = function() {
    console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
    console.log('Build Types : ');
    console.log('    \'--path=\': set the project path(设置项目路径) [required]');
    console.log('    \'--buildtype=\': set the build type(设置构建类型,值取bundle或sandbox) [required]');
    console.log('    \'--apkdir=\': set the apk output path(设置签名后的apk输出目录) [required]');
    console.log('    \'--keystore=\': keystore path(签名证书路径)  [required]');
    console.log('    \'--alias=\': keystore alias(证书别名)  [required]');
    console.log('    \'--keypass=\': keystore password(证书密码) [required]');
    console.log('    \'--aliaspass=\': keystore alias password(证书别名密码) [required]');
    console.log('    \'--configline=\': set the project config(设置项目配置) [option]');
    console.log('    \'--logpath=\': set the project path file path(设置构建log文件路径) [option]');
    process.exit(0);
};
